package ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.imp;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.BlockingQueue;

import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.DeviceController;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.ImageChangeListener;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.ProcessImageDated;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.command.Command;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.command.CommandException;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.messageprocessors.RequestProcessor;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.messageprocessors.ResponseProcessor;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.persistence.DeviceDAO;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.persistence.model.AddressValue;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.persistence.model.Device;
import net.wimpi.modbus.ModbusIOException;
import net.wimpi.modbus.io.ModbusTransport;
import net.wimpi.modbus.msg.ModbusRequest;
import net.wimpi.modbus.msg.ModbusResponse;
import net.wimpi.modbus.msg.ExceptionResponse;


public class DeviceControllerImp implements DeviceController {

	private ModbusTransport requestSocket;
	private ModbusTransport actualizationSocket;
	
	private Date lastResponde;
	 
	private int transactionNumber = 0;
	private int unitNumber = 1;
	private Vector<ModbusRequest> poolingRequests;
	private int poolingTime;

	private BlockingQueue<Command> commandsQueue;
	private BlockingQueue<AddressValue> newValues;
	
	private Map<Integer,RequestProcessor>  requestProcessor;
	private RequestProcessor requestErrorProcessor;
	private Map<Integer,ResponseProcessor> responseProcessor;
	private ResponseProcessor responseErrorProcessor;
	
	private ProcessImageDated pImage;
	private ImageChangeListener pImageListener;
	
	private DeviceDAO deviceDAO;
	private Device device;
	
	public void poolDevice(){
		
		try {
			
			while(true){
				
				for (ModbusRequest c : getPoolingRequests()) {
					ModbusResponse response;
					
					synchronized (getRequestSocket()) {
						response = sendAndReadResponse(c);
						processResponse(c,response);
					}
					
				}
				storeNewValues();
				notifyListeners();
				Thread.sleep(getPoolingTime());
			}
			
		} catch (ModbusIOException e) {
			//TODO: DeviceController.poolDevice() eats exception
		} catch (InterruptedException e) {
			//TODO: DeviceController.poolDevice() eats exception
		}
	}

	private void storeNewValues() {
		
		List<AddressValue> addValues = new LinkedList<AddressValue>();
		
		while(!newValues.isEmpty()){
			addValues.add(newValues.poll());
		}
		
		if(deviceDAO != null && device != null)
		    deviceDAO.saveValues(device,addValues);
		
	}

	public void actualizeDevice(){
		
		try {
			
			while(true){
				
				ModbusRequest req = getActualizationSocket().readRequest();
				ModbusResponse res = processRequest(req);
				getActualizationSocket().writeMessage(res);
				notifyListeners();
				
			} 
		}
		catch (ModbusIOException e) {
			//TODO: DeviceController.actualizeDevice() eats exception
		}
		
	}
	
	public void processCommands(){
		
		while(true){
			
			try {
				
				Command cmd = getCommandsQueue().take();
				ModbusRequest req;
				ModbusResponse res;
				
				try {
					
					synchronized (getRequestSocket()) {
						
						cmd.setState(Command.PROCESSING);
						
						if(cmd.needRetrieve()){
							
							req = cmd.getRetriveRequest();
							res = sendAndReadResponse(req);
							processResponse(req,res);
							cmd.setRetrieveResponse(res);
							
						}
				
						req = cmd.getCommandRequest();
						res = sendAndReadResponse(req);
						processResponse(req,res);
						
					}
					
					if(res instanceof ExceptionResponse || !(res instanceof ModbusResponse)){
						
						cmd.setState(Command.FAIL);
					
					}
					else {
						
						cmd.setState(Command.DONE);
					
					}
					
				}
				catch (ModbusIOException e) {
					
					cmd.setState(Command.FAIL);
					e.printStackTrace();
				} 
				catch (CommandException e) {
					
					cmd.setState(Command.FAIL);
					e.printStackTrace();
				} 

				
				
			} catch (InterruptedException e) {
				//TODO: DeviceController.processCommands() eats exception
				return;
			} 

		}
		
	}
	
	@Override
	public void executeCommand(Command cmd) {
		
		cmd.setState(Command.WAITING);
		getCommandsQueue().add(cmd);
		
	}
	
	private void notifyListeners() {

		if(pImageListener != null)
		    pImageListener.change();
		
	}

	@Override
	public void flushCommandQueue() {
	
		while(!commandsQueue.isEmpty()){
			Command command = commandsQueue.poll();
			command.setState(Command.FAIL);
		}
		notifyListeners();
	}
	
	public ModbusResponse sendAndReadResponse(ModbusRequest c) throws ModbusIOException {
		
		c.setTransactionID(getTransactionNumber());
		c.setUnitID(getUnitNumber());
		setTransactionNumber(getTransactionNumber() + 1);
		
		getRequestSocket().writeMessage(c);
		
		return getRequestSocket().readResponse();
		
	}

 	public ModbusResponse processRequest(ModbusRequest req) {
        
 		setLastResponde(new Date());
		
 		Integer fcode = new Integer(req.getFunctionCode());
 		
 		if(getRequestProcessor().containsKey(fcode))
 			return getRequestProcessor().get(fcode).processRequest(req);
 
 		return getErrorResponse(req);	
		
	}

	public void processResponse(ModbusRequest req, ModbusResponse res) {
		
		setLastResponde(new Date());
		
		Integer fcode = new Integer(res.getFunctionCode());
 		
 		if(getResponseProcessor().containsKey(fcode))
 			 getResponseProcessor().get(fcode).processResponse(req, res, newValues);
 		else
 			getResponseErrorProcessor().processResponse(req, res, newValues);
 		
	}

	private ModbusResponse getErrorResponse(ModbusRequest req) {
		
		return getRequestErrorProcessor().processRequest(req);
		
	}

	/**
	 * @return the requestSocket
	 */
	public ModbusTransport getRequestSocket() {
		
		return requestSocket;
		
	}

	/**
	 * @param requestSocket the requestSocket to set
	 */
	public void setRequestSocket(ModbusTransport requestSocket) {
		
		if(this.requestSocket!= null){
			
			try { 
				
				this.requestSocket.close();
				
			} 
			catch (Exception e) {
				/* No importa si tira excepcion porque no lo uso mas al socket */ 
			}
			
		}
		
		this.requestSocket = requestSocket;
	}

	/**
	 * @return the actualizationSocket
	 */
	public ModbusTransport getActualizationSocket() {
		return actualizationSocket;
	}

	/**
	 * @param actualizationSocket the actualizationSocket to set
	 */
	public void setActualizationSocket(ModbusTransport actualizationSocket) {
		
		if(this.actualizationSocket!= null){
			
			try {
				
				this.actualizationSocket.close();
				
			} 
			catch (Exception e) {
				/* No importa si tira excepcion porque no lo uso mas al socket */ 
			}
			
		}
		
		this.actualizationSocket = actualizationSocket;
	}

	/**
	 * @return the lastResponde
	 */
	public Date getLastResponde() {
		
		return lastResponde;
		
	}

	/**
	 * @param lastResponde the lastResponde to set
	 */
	public void setLastResponde(Date lastResponde) {
		
		this.lastResponde = lastResponde;
		
	}

	/**
	 * @return the transactionNumber
	 */
	public int getTransactionNumber() {
		
		return transactionNumber;
		
	}

	/**
	 * @param transactionNumber the transactionNumber to set
	 */
	public void setTransactionNumber(int transactionNumber) {
		
		// Transaction number is 2 bytes lenght so max values is 0xFFFF
		this.transactionNumber = transactionNumber % 0x10000;
		
	}

	/**
	 * @return the unitID
	 */
	public int getUnitNumber() {
		
		return unitNumber;
		
	}

	/**
	 * @param unitID the unitID to set
	 */
	@Override
	public void setUnitNumber(int unitID) {
		
		this.unitNumber = unitID;
		
	}

	/**
	 * @return the poolingRequests
	 */
	public Vector<ModbusRequest> getPoolingRequests() {
		
		return poolingRequests;
		
	}

	/**
	 * @param poolingRequests the poolingRequests to set
	 */
	public void setPoolingRequests(Vector<ModbusRequest> poolingRequests) {
		
		this.poolingRequests = poolingRequests;
		
	}

	/**
	 * @return the poolingTime
	 */
	public int getPoolingTime() {
		
		return poolingTime;
		
	}

	/**
	 * @param poolingTime the poolingTime to set
	 */
	public void setPoolingTime(int poolingTime) {
		
		this.poolingTime = poolingTime;
		
	}

	/**
	 * @return the commandsQueue
	 */
	public BlockingQueue<Command> getCommandsQueue() {
		
		return commandsQueue;
		
	}

	/**
	 * @param commandsQueue the commandsQueue to set
	 */
	public void setCommandsQueue(BlockingQueue<Command> commandsQueue) {
		
		this.commandsQueue = commandsQueue;
		
	}

	/**
	 * @return the newValues
	 */
	public BlockingQueue<AddressValue> getNewValues() {
		return newValues;
	}

	/**
	 * @param newValues the newValues to set
	 */
	public void setNewValues(BlockingQueue<AddressValue> newValues) {
		this.newValues = newValues;
	}

	/**
	 * @return the requestProcessor
	 */
	public Map<Integer, RequestProcessor> getRequestProcessor() {
		
		return requestProcessor;
		
	}

	/**
	 * @param requestProcessor the requestProcessor to set
	 */
	public void setRequestProcessor(Map<Integer, RequestProcessor> requestProcessor) {
		
		this.requestProcessor = requestProcessor;
		
	}

	/**
	 * @return the requestErrorProcessor
	 */
	public RequestProcessor getRequestErrorProcessor() {
		
		return requestErrorProcessor;
		
	}

	/**
	 * @param requestErrorProcessor the requestErrorProcessor to set
	 */
	public void setRequestErrorProcessor(RequestProcessor requestErrorProcessor) {
		
		this.requestErrorProcessor = requestErrorProcessor;
	
	}

	/**
	 * @return the responseProcessor
	 */
	public Map<Integer, ResponseProcessor> getResponseProcessor() {
		
		return responseProcessor;
	
	}

	/**
	 * @param responseProcessor the responseProcessor to set
	 */
	public void setResponseProcessor(Map<Integer, ResponseProcessor> responseProcessor) {
		
		this.responseProcessor = responseProcessor;
	
	}

	/**
	 * @return the responseErrorProcessor
	 */
	public ResponseProcessor getResponseErrorProcessor() {
		
		return responseErrorProcessor;
	
	}

	/**
	 * @param responseErrorProcessor the responseErrorProcessor to set
	 */
	public void setResponseErrorProcessor(ResponseProcessor responseErrorProcessor) {
	
		this.responseErrorProcessor = responseErrorProcessor;
	
	}

	public ProcessImageDated getImage() {
		
		return pImage;
	
	}

	public void setImage(ProcessImageDated img) {
		
		this.pImage = img;
		
	}

	@Override
	public ImageChangeListener getPImageListener() {
		
		return pImageListener;
	
	}

	@Override
	public void setPImageListener(ImageChangeListener pImageListener) {
		
		this.pImageListener = pImageListener;
	
	}

	public DeviceDAO getDeviceDAO() {
		
		return deviceDAO;
	
	}

	public void setDeviceDAO(DeviceDAO deviceDAO) {
		
		this.deviceDAO = deviceDAO;
	
	}

	public Device getDevice() {
		
		return device;
	
	}

	public void setDevice(Device device) {
		
		this.device = device;
	
	}

}
